#define PrynInternal
#define PrynImplementation
#include <pryn.h>

// Ensure that tag is non-zero, retrieve the state, execute the body, then return success. Declares state and result.
#define PrynTagFunction(BODY) \
	do { \
		PrynState *state; \
		PrynResult result = PrynResultSuccess; \
		\
		prynTagState (tag, &state); \
		if (!tag) return PrynResultNullArgument; \
		BODY \
		return PrynResultSuccess; \
	} while (0)

// Function body for retrieving a property of the tag. tag and output are expected values. output is cleared to INITIAL on failure. VALUE retrieves the field from tag.
#define PrynTagGetPropertyBase(INITIAL, VALUE) \
	do { \
		PrynState *state; \
		if (output) *output = INITIAL; \
		if (!tag) return PrynResultNullArgument; \
		prynObjectState (&tag->pObject, &state); \
		if (!output) return PrynResultNullArgument; \
		*output = VALUE; \
		return PrynResultSuccess; \
	} while (0)

// As with PrynTagGetPropertyBase, but retrieves a normal property of tag with the given name.
#define PrynTagGetProperty(INITIAL, NAME) PrynTagGetPropertyBase (INITIAL, tag->##NAME)

/// Function body for retrieving a property of the object in the tag. tag and output are expected values. output is cleared to INITIAL on failure. NAME is the name of the object property to retrieve, such as State for calling prynObjectState.
#define PrynTagGetObjectProperty(INITIAL, NAME) \
	PrynObject object; \
	PrynResult result; \
	\
	if (output) *output = INITIAL; \
	if ((result = prynTagObject (tag, &object)) < 0) return result; \
	return prynObject##NAME (&tag->pObject, output)

// Search for a matching tag. MATCH is the test for whether a given tag is what you're looking for.
#define PrynTagFind(MATCH) \
	PrynState *state = 0; \
	PrynTag *tag; \
	\
	if (output) *output = 0; \
	prynTagState (list ? list->first : 0, &state); \
	if (!output || !list) return PrynResultNullArgument; \
	\
	for (tag = list->first; tag; tag = tag->pNext) \
		if (MATCH) \
		{ \
			*output = tag; \
			return PrynResultSuccess; \
		} \
	\
	return PrynResultDone

// Search for a matching tag, then return its data value if found. NAME is the type to search, like Match for prynFindTagByMatch. ARGUMENTS are the complete arguments to that function, and must start with (&tag, list).
#define PrynTagFindData(NAME, ARGUMENTS) \
	PrynState *state; \
	PrynTag *tag; \
	PrynResult result; \
	\
	prynTagState (list ? list->first : 0, &state); \
	if (!output) return PrynResultNullArgument; \
	*output = 0; \
	result = prynTagFindBy##NAME ARGUMENTS; \
	if (result == 0) *output = tag->pData; \
	return result

PrynExport (PrynResult, prynTagFindByMatch, (PrynTag **output, PrynTagList *list, const PrynString *id, void *key))
{
	PrynTagFind (tag->pMatchFunction && !tag->pMatchFunction (tag, id, key));
}

PrynExport (PrynResult, prynTagFindByKey, (PrynTag **output, PrynTagList *list, void *key))
{
	PrynTagFind (!tag->pMatchFunction && tag->pKey == key);
}

PrynExport (PrynResult, prynTagFindById, (PrynTag **output, PrynTagList *list, const PrynString *id))
{
	PrynTagFind (!tag->pMatchFunction && !tag->pKey && !prynStringEqual (&tag->pId, id));
}

PrynExport (PrynResult, prynTagFindDataByMatch, (void **output, PrynTagList *list, const PrynString *id, void *key)) { PrynTagFindData (Match, (&tag, list, id, key)); }
PrynExport (PrynResult, prynTagFindDataByKey, (void **output, PrynTagList *list, void *key)) { PrynTagFindData (Key, (&tag, list, key)); }
PrynExport (PrynResult, prynTagFindDataById, (void **output, PrynTagList *list, const PrynString *id)) { PrynTagFindData (Id, (&tag, list, id)); }

PrynExport (PrynResult, prynTagCreate, (PrynTag **output, PrynObject *object, const PrynString *id, void *data))
{
	return prynTagCreateWithKey (output, object, 0, id, data);
}

PrynExport (PrynResult, prynTagCreateWithKey, (PrynTag **output, PrynObject *object, void *key, const PrynString *id, void *data))
{
	PrynState *state;
	PrynTagList *tags;
	PrynTag *tag;
	PrynResult result;

	if (output) *output = 0;
	prynObjectState (object, &state);
	prynObjectTags (object, &tags);
	if (!state || !output || !id) return PrynResultNullArgument;
	if (!tags) return PrynResultNullArgument;

	if ((result = prynAllocateClearedObject (state, &tag)) < 0) return result;
	
	tag->pCommonObject.pType = PrynObjectTypeTag;
	tag->pObject = *object;
	tag->pKey = key;
	tag->pId = *id;
	tag->pData = data;
	
	prynDListAppendP (*tags, tag);
	if (output) *output = tag;
	return PrynResultSuccess;
}

PrynExport (PrynResult, prynTagDestroy, (PrynTag *tag))
{
	PrynState *state;
	PrynTagList *list;

	prynTagState (tag, &state);
	prynTagList (tag, &list);
	if (!tag || !list) return PrynResultNullArgument;
	
	if (tag->pDestroyFunction) tag->pDestroyFunction (tag);
	if (list) prynDListRemoveP (*list, tag);
	prynFree (state, tag);
	return PrynResultSuccess;
}

PrynExport (PrynResult, prynTagListDestroy, (PrynTagList *list))
{
	if (!list) return PrynResultNullArgument;
	while (list->first) prynTagDestroy (list->first);
	return PrynResultSuccess;
}

PrynExport (PrynResult, prynTagState, (PrynTag *tag, PrynState **output)) { PrynTagGetObjectProperty (0, State); }
PrynExport (PrynResult, prynTagList, (PrynTag *tag, PrynTagList **output)) { PrynTagGetObjectProperty (0, Tags); }

PrynExport (PrynResult, prynTagObject, (PrynTag *tag, PrynObject *output)) { PrynObject null = { 0, 0 }; PrynTagGetProperty (null, pObject); }
PrynExport (PrynResult, prynTagData, (PrynTag *tag, void **output)) { PrynTagGetProperty (0, pData); }
PrynExport (PrynResult, prynTagKey, (PrynTag *tag, void **output)) { PrynTagGetProperty (0, pKey); }
PrynExport (PrynResult, prynTagTags, (PrynTag *tag, PrynTagList **output)) { PrynTagGetPropertyBase (0, &tag->pCommonObject.pTags); }

PrynExport (PrynResult, prynTagSetMatch, (PrynTag *tag, PrynTagMatchFunction match, void *key)) { PrynTagFunction ({ tag->pMatchFunction = match; tag->pKey = key; }); }
PrynExport (PrynResult, prynTagSetKey, (PrynTag *tag, void *key)) { PrynTagFunction ({ tag->pKey = key; }); }
PrynExport (PrynResult, prynTagSetDestroyFunction, (PrynTag *tag, PrynTagDestroyFunction destroy)) { PrynTagFunction ({ tag->pDestroyFunction = destroy; }); }
PrynExport (PrynResult, prynTagSetData, (PrynTag *tag, void *data)) { PrynTagFunction ({ tag->pData = data; }); }

static PrynResult freeTagDataFunction (PrynTag *tag)
{
	PrynTagFunction ({
		void *data;

		if ((result = prynTagData (tag, &data)) < 0) return result;
		return prynFree (state, data);
	});
}

PrynExport (PrynTagDestroyFunction, prynTagFreeDataFunction, ())
{
	return &freeTagDataFunction;
}
